/**
 * 
 */
package se.ricardo.gamesorgs.client.modes.games;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemListener;
import java.util.EventListener;
import java.util.EventObject;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.EventListenerList;

import se.ricardo.gamesorgs.api.common.ConditionScale;
import se.ricardo.gamesorgs.api.common.ListTypes;
import se.ricardo.gamesorgs.api.request.AddGameRequest;
import se.ricardo.gamesorgs.api.request.EditGameRequest;
import se.ricardo.gamesorgs.api.request.GamesOptionsRequest;
import se.ricardo.gamesorgs.database.models.GameContainer;

/**
 * Class that extends {@link JDialog}, for displaying
 * a form, that the user may use to add/edit games in
 * the database.
 * <br>
 * 
 * @author Ricardo Ocaña Leal
 *
 */
public class GamesEditDialog extends JDialog {
	/**
	 * Enum to keep track of which mode the dialog
	 * currently is in.*/
	public enum DialogMode {
		/**
		 * Set the dialog mode to add a new game.
		 * */
		ADD,
		/**
		 * Set the dialog mode to edit an existing game.
		 * */
		EDIT
	}
	
	// GridbagLayout objects for the form.
	private GridBagLayout gbLayout = null;
	private GridBagConstraints lastConstraints = null;
	private GridBagConstraints middleConstraints = null;
	private GridBagConstraints labelConstraints = null;
	private JPanel panel;
	
	// Form fields.
	private JTextField titleField;
	private JComboBox<String> companyCombo;
	private JComboBox<String> systemsCombo;
	private JComboBox<String> cartCombo;
	private JComboBox<String> instrCombo;
	private JComboBox<String> boxCombo;
	private JTextField commentField;
	private JCheckBox collectionBox;
	private JCheckBox wishBox;
	private JCheckBox saleBox;
	private ButtonGroup checkGroup;
	
	// List for storing all registered listeners.
	private EventListenerList listenerList = new EventListenerList();
	
	private GameContainer currentGame;
	private int previousListType;
	private DialogMode mode;
	
	/**
	 * Initializes the JDialog.
	 * <br>
	 * 
	 * @param
	 * owner The Frame that requests the JDialog to show.
	 * @param
	 * modal Boolean to decided if the JDialog should block the owner.
	 * */
	public GamesEditDialog(JFrame owner, boolean modal) {
		super(owner, modal);
		
		setLayout(new BorderLayout());
		setBounds(20, 20, 450, 300);
		setupGridBag();
		createForm();
		createButtons();
		
		mode = DialogMode.ADD;
		currentGame = null;
	}
	
	/**
	 * Sets up the layout for the form.
	 * */
	private void setupGridBag() {
		lastConstraints = new GridBagConstraints();
		lastConstraints.fill = GridBagConstraints.HORIZONTAL;
		lastConstraints.anchor = GridBagConstraints.NORTHWEST;
		lastConstraints.weightx = 1.0;
		lastConstraints.gridwidth = GridBagConstraints.REMAINDER;
		lastConstraints.insets = new Insets(1, 10, 1, 10);
		
		middleConstraints = 
				(GridBagConstraints)lastConstraints.clone();
		middleConstraints.gridwidth = GridBagConstraints.RELATIVE;
		
		labelConstraints = 
				(GridBagConstraints)lastConstraints.clone();
		labelConstraints.weightx = 0.0;
		labelConstraints.gridwidth = 1;
	}
	
	/**
	 * Creates the form and sets up the layout for all the
	 * components.
	 * <br>
	 * */
	private void createForm() {
		gbLayout = new GridBagLayout();
		panel = new JPanel(gbLayout);
		
		addLabel("Title", panel);
		titleField = new JTextField(50);
		addField(titleField, panel);
		
		addLabel("Company", panel);
		companyCombo = new JComboBox<>();
		addField(companyCombo, panel);
		
		addLabel("System", panel);
		systemsCombo = new JComboBox<>();
		addField(systemsCombo, panel);
		
		addLabel("Cartridge/Disc", panel);
		cartCombo = new JComboBox<>(ConditionScale.getConditionArray());
		addField(cartCombo, panel);
		
		addLabel("Instructions", panel);
		instrCombo = new JComboBox<>(ConditionScale.getConditionArray());
		addField(instrCombo, panel);
		
		addLabel("Box", panel);
		boxCombo = new JComboBox<>(ConditionScale.getConditionArray());
		addField(boxCombo, panel);
		
		addLabel("Comment", panel);
		commentField = new JTextField(255);
		addField(commentField, panel);
		
		JPanel p = new JPanel(new GridLayout(0, 3));
		checkGroup = new ButtonGroup();
		collectionBox = new JCheckBox("Collection");
		wishBox = new JCheckBox("Wish List");
		saleBox = new JCheckBox("For Sale");
		checkGroup.add(collectionBox);
		checkGroup.add(wishBox);
		checkGroup.add(saleBox);
		collectionBox.setSelected(true);
		
		p.add(collectionBox);
		p.add(wishBox);
		p.add(saleBox);
		
		addLabel("List", panel);
		addField(p, panel);
		
		add(panel, BorderLayout.NORTH);
	}
	
	/**
	 * Add a "label" to the form.
	 * <br>
	 * 
	 * @param
	 * text The name of the label.
	 * @param
	 * parent The container of the label.
	 * */
	private void addLabel(String text, Container parent) {
		addLabel(new JLabel(text), parent);
	}
	
	/**
	 * Add a "label" to the form.
	 * The label does not necessarily have to be a JLabel.
	 * <br>
	 * 
	 * @param
	 * c The component to be added as a label.
	 * @param
	 * parent The container of the label.
	 * */
	private void addLabel(Component c, Container parent) {
		gbLayout.setConstraints(c, labelConstraints);
		parent.add(c);
	}
	
	/**
	 * Add a "field" to the form.
	 * <br>
	 * 
	 * @param
	 * c The field to add.
	 * @param
	 * parent The container of the field.
	 * */
	private void addField(Component c, Container parent) {
		gbLayout.setConstraints(c, lastConstraints);
		parent.add(c);
	}
	
	/**
	 * Add a field that will be followed by another field.
	 * <br>
	 * 
	 * @param
	 * c The field to add.
	 * @param
	 * parent The container of the field.
	 * */
	private void addMiddleField(Component c, Container parent) {
		gbLayout.setConstraints(c, middleConstraints);
		parent.add(c);
	}
	
	/**
	 * Creates {@link JButton}s for the Dialog.
	 * */
	private void createButtons() {
		JPanel panel = new JPanel(new BorderLayout());
		panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 10));
		JPanel innerPanel = new JPanel(new GridLayout(0, 2, 5, 0));
		
		JButton okButton = new JButton("OK");
		okButton.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent evt) {
				if(!titleField.getText().equals("")) {
					fireInputReadyEvent(new InputReadyEvent(this));					
				}
			}
		});
		
		JButton cancelButton = new JButton("Cancel");
		cancelButton.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent evt) {
				setVisible(false);
				resetDialog();
			}
		});
		
		innerPanel.add(okButton);
		innerPanel.add(cancelButton);
		
		panel.add(innerPanel, BorderLayout.EAST);
		
		add(panel, BorderLayout.SOUTH);
	}
	
	/**
	 * Used to show the dialog at a specific coordinate.
	 * x, y decide the top-left corner of the dialog.<br>
	 * To hide the dialog it is easier to use the other
	 * showDialog(boolean b) method.
	 * <br>
	 * 
	 * @param
	 * x Coordinate on x-axis.
	 * @param
	 * y Coordinate on y-axis.
	 * @param
	 * b The boolean to decide whether to show or hide the dialog.
	 * */
	public void setVisible(int x, int y, boolean b) {
		int dialogX = x;
		int dialogY = y;
		
		// TODO: NOT WORKING!
		// Dialog does not appear in the center!
		if(x != 0 && y != 0) {
			dialogX -= (getWidth()/2);
			dialogY -= (getHeight()/2);
		}
		
		setLocation(dialogX, dialogY);
		setVisible(b);
	}
	
	/**
	 * Sets the current companies to the JComboBox.
	 * <br>
	 * 
	 * @param
	 * companies An array with Objects of the companies.
	 * */
	public void setCompanies(Object[] companies) {
		for(Object s : companies) {
			companyCombo.addItem((String)s);
		}
	}
	
	/**
	 * Call to fill the Form with the values of the currently
	 * selected game.
	 * <br>
	 * 
	 * @param
	 * game Reference to a {@link GameContainer} object of the
	 * selected game.
	 * @param
	 * request A {@link GamesOptionsRequest} containing values to
	 * be set in the dialog.
	 * @param
	 * systems Array with the current systems.
	 * */
	public void fillForm(
			GameContainer game,
			GamesOptionsRequest request,
			Object[] systems) {
		currentGame = game;
		previousListType = request.getListType();
		
		titleField.setText(game.getTitle());
		cartCombo.setSelectedIndex(
				ConditionScale.getConditionIndex(game.getCartride()));
		instrCombo.setSelectedIndex(
				ConditionScale.getConditionIndex(game.getInstructions()));
		boxCombo.setSelectedIndex(
				ConditionScale.getConditionIndex(game.getBox()));		
		commentField.setText(game.getComment());
		
		switch(previousListType) {
			case ListTypes.COLLECTION:
				collectionBox.setSelected(true);
				break;
			case ListTypes.WISH:
				wishBox.setSelected(true);
				break;
			case ListTypes.SALE:
				saleBox.setSelected(true);
				break;
			default:
				collectionBox.setSelected(true);					
		}
		
		fillForm(request, systems);
	}
	
	/**
	 * Fills the Form with the current values.
	 * <br>
	 * 
	 * @param
	 * request A {@link GamesOptionsRequest} containing values to
	 * be set in the dialog.
	 * @param
	 * systems Array with the current systems.
	 * */
	public void fillForm(GamesOptionsRequest request,
			Object[] systems) {
		companyCombo.setSelectedIndex(request.getCompanyIndex());
		updateSystems(systems, request.getSystemsIndex());
	}
	
	/**
	 * Called to update the Forms' sytems {@link JComboBox}
	 * with values corresponding to the selected company.
	 * <br>
	 * 
	 * @param
	 * systems Array with the current systems.
	 * @param
	 * systemsIndex The index to set selected in the
	 * systems combobox.
	 * */
	public void updateSystems(Object[] systems, int systemsIndex) {
		systemsCombo.removeAllItems();
		// Populate the systems combobox.
		for(Object s : systems) {
			systemsCombo.addItem((String)s);
		}
		systemsCombo.setSelectedIndex(systemsIndex);
	}
	
	/**
	 * Returns a new {@link AddGameRequest} with all the
	 * data from the form.
	 * <br>
	 * 
	 * @return
	 * A reference to a new {@link AddGameRequest} object.
	 * */
	public AddGameRequest getRequest() {
		switch(mode) {
			case ADD:
				return createAddRequest();
			case EDIT:
				return createEditRequest();
		}
		
		return null;
	}
	
	/**
	 * Creates and returns an {@link AddGameRequest}.
	 * <br>
	 * 
	 * @return
	 * A reference to a new {@link AddGameRequest} object.
	 * */
	private AddGameRequest createAddRequest() {
		return new AddGameRequest(
				titleField.getText(),
				(String)companyCombo.getSelectedItem(),
				(String)systemsCombo.getSelectedItem(),
				(String)cartCombo.getSelectedItem(),
				(String)instrCombo.getSelectedItem(),
				(String)boxCombo.getSelectedItem(),
				commentField.getText(),
				getSelectedListType());
	}
	
	/**
	 * Creates and returns an {@link EditGameRequest}.
	 * <br>
	 * 
	 * @return
	 * A reference to a new {@link EditGameRequest} object.
	 * */
	private EditGameRequest createEditRequest() {
		return new EditGameRequest(
				currentGame.getId(),
				titleField.getText(),
				(String)companyCombo.getSelectedItem(),
				(String)systemsCombo.getSelectedItem(),
				(String)cartCombo.getSelectedItem(),
				(String)instrCombo.getSelectedItem(),
				(String)boxCombo.getSelectedItem(),
				commentField.getText(),
				getSelectedListType(),
				previousListType);
	}
	
	/**
	 * Verifies which list type checkbox is currently selected,
	 * and returns the corresponding {@link ListTypes} constant.
	 * <br>
	 * 
	 * @return
	 * {@link ListTypes} constant.
	 * */
	private int getSelectedListType() {
		int listType = 0;
		
		if(collectionBox.isSelected()) {
			listType = ListTypes.COLLECTION;
		}
		else if(wishBox.isSelected()) {
			listType = ListTypes.WISH;
		}
		else if(saleBox.isSelected()) {
			listType = ListTypes.SALE;
		}
		
		return listType;
	}
	
	/**
	 * Resets the form to default values.
	 * */
	public void resetDialog() {
		titleField.setText("");
		companyCombo.setSelectedIndex(0);
		systemsCombo.setSelectedIndex(0);
		cartCombo.setSelectedIndex(0);
		instrCombo.setSelectedIndex(0);
		boxCombo.setSelectedIndex(0);
		commentField.setText("");
		collectionBox.setSelected(true);
	}
	
	/**
	 * Register event to fire when the form is ready to send
	 * the data.
	 * <br>
	 * 
	 * @param
	 * l The {@link InputReadyListener} to register to the form.
	 * */
	public void addInputReadyListener(InputReadyListener l) {
		listenerList.add(InputReadyListener.class, l);
	}
	
	/**
	 * Add an {@link ItemListener} to the company {@link JComboBox},
	 * to update the form when the user changes the selection.
	 * <br>
	 * 
	 * @param
	 * l The ItemListener to register.
	 * */
	public void addCompanyItemListener(ItemListener l) {
		companyCombo.addItemListener(l);
	}
	
	/**
	 * Returns the index of the currently selected company
	 * in the combobox.
	 * <br>
	 * 
	 * @return
	 * int containing the company index.
	 * */
	public int getCompanyIndex() {
		return companyCombo.getSelectedIndex();
	}
	
	/**
	 * Returns the mode that the dialog is currently set to.
	 * <br>
	 * 
	 * @return
	 * {@link DialogMode} value.
	 * */
	public DialogMode getDialogMode() {
		return mode;
	}
	
	/**
	 * Sets the dialog mode to either add or edit.<br>
	 * Value can be selected from the enum {@link DialogMode}.
	 * <br>
	 * 
	 * @param
	 * mode The Dialog mode to set.
	 * */
	public void setDialogMode(DialogMode mode) {
		this.mode = mode;
	}
	
	/**
	 * Fires event to all registered listeners.
	 * */
	private void fireInputReadyEvent(InputReadyEvent evt) {
		for(Object l : listenerList.getListenerList()) {
			if(l instanceof InputReadyListener) {
				((InputReadyListener) l).dialogInputReady(evt);
			}
		}
	}
	
	/**
	 * Custom event class.
	 * */
	// TODO: Class and interface should be placed in own class file.
	public class InputReadyEvent extends EventObject {
		public InputReadyEvent(Object source) {
			super(source);
		}
	}
	
	/**
	 * Listener interface for registering that the form is ready.
	 * */
	public interface InputReadyListener extends EventListener {
		public void dialogInputReady(InputReadyEvent evt);
	}
}
